<?php /** @noinspection DuplicatedCode */

/** @noinspection PhpUndefinedMethodInspection */

namespace app\console\task\tetr;

use app\common\library\Measurement;
use app\console\model\Room;
use Exception;
use RollingCurl\Request;
use RollingCurl\RollingCurl;
use think\facade\Cache;
use Throwable;

/**
 * 批量刷新游戏状态
 * Class RefreshGamePlayStatus
 * @package app\console\task\tetr
 */
class RefreshGamePlayStatus extends BasicTetrTask
{
    // 当前任务唯一标识
    private string $taskKey = 'RefreshGamePlayStatus';

    // 任务执行间隔时长 (单位:秒)
    protected int $taskExpire = 10 * 60;

    // 当前已完成扫描数
    private int $scanCount = 0;

    /**
     * 任务处理
     * @param array $param
     */
    public function handle(array $param)
    {
        $this->setInterval($this->taskKey, $this->taskExpire, function () {
            try {
                // 统计开始时间
                $startTime = microtime(true);
                $this->safeEcho("开始扫描房间游玩状态...");
                $this->scanCount = 0;
                Room::chunk(100, function ($rooms) {
                    $ids = array_column($rooms->toArray(), 'room_id');
                    $this->updateGamePlayStatus($ids);
                });
                // 输出一次扫描时长
                Cache::set(Measurement::RGSRefreshTime, microtime(true) - $startTime);
                Cache::set(Measurement::RGSScanCount, Cache::get(Measurement::RGSScanCount, 0) + 1);
                $this->safeEcho(sprintf("游玩状态更新用时: %.3f 秒", microtime(true) - $startTime));
            } catch (Exception $e) {
                $this->safeEcho(sprintf("更新游玩状态失败: %s", $e->getMessage()));
            }
            return true;
        });
    }

    /**
     * 更新游戏状态
     * @param $roomIds
     * @return void
     * @throws Exception
     */
    private function updateGamePlayStatus($roomIds)
    {
        // 滚动式Curl实例
        $rollingCurl = new RollingCurl();
        $rollingCurl->setOptions([
            CURLOPT_TIMEOUT        => 10, // 超时时间
            CURLOPT_RETURNTRANSFER => true, // 返回数据
            CURLOPT_FOLLOWLOCATION => true, // 跟随重定向
            CURLOPT_SSL_VERIFYPEER => false, // 不验证证书
            CURLOPT_SSL_VERIFYHOST => false, // 不验证主机
        ]);

        // 添加任务
        foreach ($roomIds as $room) {
            $url = 'https://api.live.bilibili.com/xlive/open-platform/v1/gameInterface/getInfoByRoom?room_id=' . $room;
            $request = new Request($url);
            $request->setExtraInfo(['room_id' => $room, 'retry' => 0]);

            // 获取一个代理地址
            $proxy = $this->whileGetProxy();
            $request->addOptions([
                CURLOPT_PROXY      => $proxy[0],
                CURLOPT_PROXYPORT  => $proxy[1],
                CURLOPT_HTTPHEADER => [
                    'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
                    'Referer: https://live.bilibili.com/' . $room,
                ],
            ]);

            // 添加请求成功回调
            $rollingCurl->add($request);
        }

        // 设置回调
        $rollingCurl->setSimultaneousLimit(5); // 并发数
        $rollingCurl->setCallback(function (Request $request, RollingCurl $rollingCurl) {

            // 从额外信息中获取房间ID
            $room_id = $request->getExtraInfo()['room_id'];
            Cache::set(Measurement::RGSScanTime, time()); // 更新最后扫描时间

            try {

                // 请求的响应码不为0
                if ($request->getResponseErrno() !== 0) {
                    throw new Exception(sprintf("请求失败[RID->%s]：[%s] %s", $room_id, $request->getResponseErrno(), $request->getResponseError()));
                }

                // 回来的响应体不能为空
                if (empty($request->getResponseText())) {
                    throw new Exception(sprintf("返回异常[RID->%s]: 响应体为空 %s", $room_id, json_encode($request->getResponseInfo())));
                }

                // 尝试解开Json
                $data = json_decode($request->getResponseText(), true);
                if (json_last_error()) {
                    throw new Exception(sprintf("解析失败[RID->%s]: [%s] %s", $room_id, json_last_error(), json_last_error_msg()));
                }

                // 获取当前房间的模型
                $roomModel = Room::detail(['room_id' => $room_id]);
                if (!$roomModel) {
                    throw new Exception(sprintf("房间不存在[RID->%s]", $room_id));
                }

                // 到这里就已经扫描成功了
                $this->scanCount++;
                Cache::set(Measurement::RGSChunkCount, $this->scanCount);

                // 如果返回的code为7000则表示没有在运行的互动玩法
                if ($data['code'] == 7000) {
                    $this->safeEcho(sprintf("未启动玩法 -> %s", $room_id));
                    $roomModel->save(['tetris_status' => 0]);
                    return true;
                }

                // 本次请求成功
                if ($data['code'] == 0) {
                    if (is_array($data['data']) && isset($data['data']['game_list'])) {
                        $gameList = array_column($data['data']['game_list'], null, 'game_code');
                        if (array_key_exists('1659261256750', $gameList)) {
                            $this->safeEcho(sprintf("已启动方块 -> %s", $room_id));
                            $roomModel->save(['tetris_status' => 1, 'last_tetris_time' => time()]);
                        } else {
                            $this->safeEcho(sprintf("未启动方块 -> %s", $room_id));
                            $roomModel->save(['tetris_status' => 0]);
                        }
                        return true;
                    }
                }

                return true;

            } catch (Throwable $throwable) {

                // 如果重试次数大于3次不执行本条任务
                if ($request->getExtraInfo()['retry'] > 3) {
                    $this->scanCount++;
                    Cache::set(Measurement::RGSChunkCount, $this->scanCount);
                    $this->safeEcho('任务失败放弃: ' . $throwable->getMessage());
                    return false;
                }

                // 否则获取新的代理ip然后重试该任务
                $proxy = $this->whileGetProxy();
                $request->setExtraInfo(['room_id' => $room_id, 'retry' => $request->getExtraInfo()['retry'] + 1]);
                $request->addOptions([CURLOPT_PROXY => $proxy[0], CURLOPT_PROXYPORT => $proxy[1]]);
                $rollingCurl->add($request);
                $this->safeEcho(sprintf('重试 -> %s', $throwable->getMessage()));

                return true;
            }
        });

        // 执行批量Curl请求
        $rollingCurl->execute();
    }

    /**
     * 获取代理地址
     * @return array
     */
    private function whileGetProxy(): array
    {
        $proxy = $this->getProxyAddress();
        while (false === $proxy) {
            $this->safeEcho("代理地址获取失败, 重试中...");
            $proxy = $this->getProxyAddress();
        }
        return $proxy;
    }
}
